﻿using System;
using System.Linq;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Surface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Data.Surface;

namespace RayDen.RayEngine.Materials {
    public class Material {

        protected BxDFBase[] Bxdfs;
        public int MaterialId;
        public string MaterialName;

        public Material(int id) {
            this.MaterialId = id;
            Bxdfs = new BxDFBase[4];
        }

        public RgbSpectrum F(ref Vector wi, ref Vector wo, ref Normal n, BxDFType type) {
            var current = type;
            var result = new RgbSpectrum();

            if (((Vector.Dot(ref wi, ref n)*Vector.Dot(ref wo, ref n)) > 0))
            {
                current = type & ~BxDFType.Reflection;
            }
            else {
                current = type & ~BxDFType.Transmission;
            }

            for (int index = 0; index < Bxdfs.Length; index++) {
                if (Bxdfs[index].Type.HasFlag(current))
                    result += new RgbSpectrum(Bxdfs[index].Eval(ref wo, ref wi, ref n));
            }
            return result;
        }
        public static bool MatchesFlags(BxDFType obj, BxDFType flags)
        {
            return (flags & obj).Equals(flags) || (flags & obj).Equals(obj);
        }

        public int NumComponents(BxDFType flags) {
            var res = Bxdfs.Count(p => MatchesFlags(p.Type, flags));
            return res;
        }

        public void Sample(ref Vector wo, ref Normal N, ref Normal shadeN, float u0, float u1, float u2,float u3,
                           out BsdfSample sample, BxDFType type = BxDFType.AllTypes) {
            sample = new BsdfSample();
            var fl = type;
            int matchingComps = NumComponents(fl);
            if (matchingComps == 0) {
                sample.Pdf = 0f;
                sample.Wi = Vector.Zero;
                sample.Spectrum = new RgbSpectrum(0f).ToArray();
            }
            int which = (int)Math.Min((u0 * matchingComps),
                matchingComps - 1);
            BxDFBase bxdf = null;
            int count = which;
            for (int i = 0; i < Bxdfs.Length; ++i)
                if (Bxdfs[i].Type.HasFlag(fl))
                    if (count-- == 0) {
                        bxdf = Bxdfs[i];
                        break;
                    }
            Vector wi = new Vector();
            var pdf = 0f;
            bxdf.Sample(ref wo, ref N, ref shadeN, u1,  u2, u3, out sample, type);
            wi = sample.Wi;
            pdf = sample.Pdf;
            var sampled = bxdf.Type;
            if (pdf > 0f && pdf < MathLab.Epsilon) sample.Spectrum = new float[3]{0f,0f,0f};
            //if (sampledTy != null) sampledType = bxdf.Type;
            //wiW = LocalToWorld(wi);

            if ((!bxdf.Type.HasFlag(BxDFType.Specular)) && matchingComps > 1) {
                for (int i = 0; i < Bxdfs.Length; ++i) {
                    if (Bxdfs[i] != bxdf && (Bxdfs[i].Type.HasFlag(fl)))
                        pdf += Bxdfs[i].Pdf(ref wo, ref wi, fl);
                }
            }
            if (matchingComps > 1) pdf /= matchingComps;
            // Compute value of BSDF for sampled direction
            if (bxdf.Type.HasFlag(BxDFType.Specular))
            //if ((bxdf.Type & BxDFType.BSDF_SPECULAR) == 0)
            {
                var f = RgbSpectrum.ZeroSpectrum();
                if ((Vector.Dot(ref N,ref wi)) * Vector.Dot(ref N, ref wo) > 0f)
                // ignore BTDFs
                {
                    fl = fl & ~BxDFType.Transmission;
                }
                else
                    // ignore BRDFs
                    fl = (fl & ~BxDFType.Reflection);
                for (int i = 0; i < Bxdfs.Length; ++i)
                    if ((Bxdfs[i].Type.HasFlag(fl)))
                        f +=  new RgbSpectrum(Bxdfs[i].Eval(ref wo, ref wi, ref N));
                sample.Spectrum = (f/pdf).ToArray();
            }
        }





    }
}
